#include "fractal.fx"

static const float4 RED        =    float4(1.0f, 0.1f, 0.1f, 1.0f);
static const float4    GREEN    =    float4(0.1f, 1.0f, 0.1f, 1.0f);
static const float4    BLUE    =    float4(0.1f, 0.1f, 1.0f, 1.0f);

static const float    MIN            =    -5.5f;
static const float    MAX            =     5.5f;
static const float3    BoxMin        =    (float3)MIN;
static const float3 BoxMax        =    (float3)MAX;
static const uint    INTERVALS    =    500;


struct VS_IN
{
    uint    vertexId    :    SV_VertexID;
};

struct VS_OUT
{
    float4    position    :    SV_POSITION;
    float2    coords        :    COORDS;
};

struct GS_OUT
{
    float4    position    :    SV_POSITION;
    float2    coords        :    COORDS;
    uint    arrayIndex    :    SV_RenderTargetArrayIndex;
};


struct Ray
{
    float3 o;
    float3 d;
};


//_____V A R I A B L E S__________________________________________________________________



float3x3    mArrFixedView[4] =    {
                                    float3x3(0.724835, -0.116553, -0.678992,
                                            -5.43892e-007, 0.985585, -0.169182,
                                            0.688923, 0.12263, 0.714386
                                            ),
                                    float3x3(0.717912, -0.398793, -0.570585,
                                            5.06639e-007, 0.819648, -0.572868,
                                            0.696134, 0.411268, 0.588435
                                            ),
                                    float3x3(0.724835, -0.377546, -0.576258,
                                            -5.06639e-007, 0.836463, -0.548024,
                                            0.688923, 0.397227, 0.606297
                                            ),
                                    float3x3(0.724835, -0.377546, -0.576258,
                                            -5.06639e-007, 0.836463, -0.548024,
                                            0.688923, 0.397227, 0.606297
                                            )
                                };

float3        vArrFixedEyePos[4]    = { float3(3.54173, 0.824921, -3.5242),
                                    float3(5.98055, 5.64435, -6.1533),
                                    float3(13.4577, 12.52, -14.2657),
                                    float3(13.4577, 12.52, -14.2657)
                                };


float4x4    World;
float4x4    View;
float4x4    Projection;
float3        eyePos;
float4        backgroundColor = float4( 0.6f,  0.7f,  0.5f, 1.0f);
float4        lightColor        = float4( 1.0f,  1.0f,  1.0f, 1.0f);
float3        lightPosition    = float3(10.0f, 60.0f, 10.0f);
float        nearPlane        = 1;
float        farPlane        = 500;
float        Time;
uint        iArrayIndex;
bool        bFixedView;
int			iDepth;


//_____F U N C T I O N S__________________________________________________________________

static const float3 Zero  = float3 ( 0.0, 0.0, 0.0 );
static const float3 Unit  = float3 ( 1.0, 1.0, 1.0 );
static const float3 AxisX = float3 ( 1.0, 0.0, 0.0 );
static const float3 AxisY = float3 ( 0.0, 1.0, 0.0 );
static const float3 AxisZ = float3 ( 0.0, 0.0, 1.0 );
static const float STEP   = 0.01f;

//___implicit objects__________________




float implicitSphere(float3 pos)
{
    return pos.x*pos.x + pos.y*pos.y + pos.z*pos.z - 1.3;
}

float implicitDingDong(float3 pos)
{
    return pos.x*pos.x + pos.z*pos.z - (1-pos.y)*pos.y*pos.y;
}

// x^3 + y^3 + z^3 - (x+y+z) = 0
float implicitObj1(float3 pos)
{
    float ret;
    ret =  pos.x*pos.x*pos.x;
    ret += pos.y*pos.y*pos.y;
    ret += pos.z*pos.z*pos.z;
    ret -= (pos.x+pos.y+pos.z);

    return  ret;
}

// x^2 + y^2 + z^2 + 2xyz - 1 = 0
float implicitObj2(float3 p)
{
    float ret;

    ret =  p.x*p.x;
    ret += p.y*p.y;
    ret += p.z*p.z;
    ret -= 2*p.x*p.y*p.z;
    ret -= 1.0f;

    return ret;
}

float implicitCayleyCubic(float3 p)
{
    float ret = p.x*p.x*p.y;
    ret += p.x*p.x*p.z;
    ret += p.y*p.y*p.x;
    ret += p.y*p.y*p.z;
    ret += p.z*p.z*p.x;
    ret += p.z*p.z*p.y;
    ret *= -5.0f;
    ret += 2.0f*(p.x*p.y+p.x*p.z+p.z*p.y);

    return ret;
}

float implicitGoursatTangle(float3 p)
{
    float x2 = p.x * p.x;
    float y2 = p.y * p.y;
    float z2 = p.z * p.z;

    float x4 = x2 * x2;
    float y4 = y2 * y2;
    float z4 = z2 * z2;

    float ret;
    float a,b,c;

    a = 0; b = -5.0f; c = 12.0f;

    ret =  x4 + y4 + z4;
    ret += a*(x2 + y2 + z2);
    ret += b*(x2 + y2 + z2);
    ret += c;

    return ret;
}

float implicitCSG(float3 p)
{
    float x2 = p.x * p.x;
    float y2 = p.y * p.y;
    float z2 = p.z * p.z;

    float x4 = x2 * x2;
    float y4 = y2 * y2;
    float z4 = z2 * z2;

    float ret;
    float a,b,c;

    a = 0; b = 0.0f; c = -1.0f;

    ret =  x4 + y4 + z4;
    ret += a*(x2 + y2 + z2);
    ret += b*(x2 + y2 + z2);
    ret += c;

    float ret1 = ret;
    ////
    float ret2 = implicitSphere(p);

    ret = min(ret1, ret2);
    return ret;

}



//____________________________________


//############################################
    static const uint objectId    =    4;
//############################################

float Function ( float3 pos )
{

    if      (0 == iArrayIndex)
        return implicitCSG(pos);
    else if (1 == iArrayIndex)
        return implicitGoursatTangle(pos);
    else if (2 == iArrayIndex)
        return implicitObj2(pos);
    else if (3 == iArrayIndex)
        return inside(pos); 
    else
        return implicitCSG(pos);

}

float3 CalcNormal ( float3 Position )
{
    float A = Function ( Position + AxisX * STEP )
                - Function ( Position - AxisX * STEP );
    float B = Function ( Position + AxisY * STEP )
                - Function ( Position - AxisY * STEP );
    float C = Function ( Position + AxisZ * STEP )
                - Function ( Position - AxisZ * STEP );
    return normalize ( float3 ( A, B, C ) );
}

bool IntersectSurface ( in Ray ray, in float start, in float final, out float val )
{
    float step = ( final - start ) / float ( INTERVALS );
    float time = start;
    float3 Position = ray.o + time * ray.d;
    float right, left = Function( Position );
    for ( uint i = 0; i < INTERVALS; ++i )
    {
        time += step;
        Position += step * ray.d;
        right = Function(Position);
        if ( left * right < 0.0 )
        {
            val = time + right * step / ( left - right );
            return true;
        }
        left = right;
    }
    return false;
}

bool IntersectBox ( in Ray ray, in float3 minimum, in float3 maximum,
                    out float timeIn, out float timeOut )
{
    float3 OMIN = ( minimum - ray.o ) / ray.d;
    float3 OMAX = ( maximum - ray.o ) / ray.d;
    float3 MAX = max ( OMAX, OMIN );
    float3 MIN = min ( OMAX, OMIN );
    timeOut = min ( MAX.x, min ( MAX.y, MAX.z ) );
    timeIn = max ( max ( MIN.x, 0.0 ), max ( MIN.y, MIN.z ) );
    return timeOut > timeIn ;
}


float4 proc_texture(float3 pos)
{
    float4 val = float4( abs(pos.x), abs(pos.y), abs(pos.z), 1.0f) ;
    val /= 1.0f;
    return val;
}


float4 Phong(float3 n, float3 l, float3 v, float shininess, float4 diffuseColor, float4 specularColor)
{

    float NdotL = dot(n,l);
    float diff = saturate(NdotL);
    float3 r = reflect(l,n);
    float spec = pow(saturate(dot(v,r)), shininess) * (NdotL > 0.0);
    return diff * diffuseColor + spec * specularColor;
}

// pos, normal, view, hitobj, lightIntensity
float4 Shade(float3 i, float3 n, float3 v, int hitobj, float lightIntensity)
{
    float3 l = normalize(lightPosition - i);

    Ray shadowray;
    shadowray.o = i;
    shadowray.d = l;
 

    float4 diff = float4(0.7f, 0.7f, 0.8f, 1.0f); 
    float4 spec = float4(1.0f, 1.0f, 1.0f, 1.0f); 

    float4 tex_color = proc_texture(i);


    return (lightColor * Phong(n, l, v, 20, diff, spec))  * tex_color + (float4)0.1f;
}

float4 Raytrace ( Ray ray )
{
 

    float4 result = (float4)0;
    float start, final;
    float t;

    Ray currentRay = ray;
    int depth = iDepth;

    for (int i=0; i<depth; i++)
    {
        if ( IntersectBox ( ray, BoxMin, BoxMax, start, final ) )
        {
            if ( IntersectSurface ( ray, start, final, t ) )
            {
                float3 Position = ray.o + ray.d * t;

                float3 normal;
                if (objectId == 7 || objectId == 8)
                    normal = normalize(cross(ddx(Position), ddy(Position)));
                else
                    normal = CalcNormal ( Position );


                float3 color = ( Position - BoxMin) / ( BoxMax - BoxMin);
                result += Shade(Position, CalcNormal(Position), ray.d, 0, 5);

                currentRay.d = reflect(currentRay.d, normal);
                currentRay.o = Position;
                start = 0.001f;
                t = 0.0f;

                // Simple shading
                float s = max(0.1f, dot(normalize(Position), normalize(float3(1,1,-1))));
                float r = s;
				if (iArrayIndex == 3)
					result = float4(r,r,r,1.0f) * proc_texture(Position);
            }
        }
    }
    return result;
}


//_____S H A D E R S__________________________________________________________________


VS_OUT VS(VS_IN vin)
{
    VS_OUT vout;

    float2 coords[4] = { float2(-1, 1),    float2(1, 1), float2(-1,-1), float2(1,-1) };

    vout.coords = coords[vin.vertexId];
    vout.position = float4(vout.coords, 0, 1);
    return vout;
}

[maxvertexcount(3)]
void GS_Implicit(triangle VS_OUT gin[3], inout TriangleStream<GS_OUT> gstream)
{
    GS_OUT gout;

    for (int i=0; i<3; i++) {
        gout.position = gin[i].position;
        gout.coords   = gin[i].coords;
        gout.arrayIndex = iArrayIndex;
        gstream.Append(gout);
    }
}


float4 PS_Implicit(VS_OUT input) : SV_TARGET
{

    Ray eyeray;
    eyeray.o = vArrFixedEyePos[iArrayIndex].xyz;
    float3 dir;

    dir.xy = input.coords;
    dir.z = 3.0f * nearPlane;

    float3x3   ViewInverse = (float3x3)transpose(mArrFixedView[iArrayIndex]);
    eyeray.d = mul(dir, ViewInverse);
    eyeray.d = normalize(eyeray.d);
    //


    float4 pixColour = Raytrace(eyeray);
    return pixColour;
}

DepthStencilState EnableDepth
{
    DepthEnable = TRUE;
    DepthWriteMask = ALL;
    DepthFunc = LESS_EQUAL;
};

BlendState NoBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = FALSE;
};



float4 TestShader(VS_OUT input) : SV_Target
{
    float3 oddRow = int(input.position.x) % 2 == 1 ? (float3)1.0f : (float3)0.0f;
    float oddColumn = int(input.position.y) % 2 == 1 ? 1.0f : 0.0f;
    float3 xDerivative = ddx(oddRow);
    float yDerivative = ddy(oddColumn);
    return float4(xDerivative, 1.0f);
}


technique10 Implicit
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetHullShader(NULL);
        SetDomainShader(NULL);
        SetGeometryShader( CompileShader( gs_4_0, GS_Implicit() ) );
        SetPixelShader( CompileShader( ps_4_0, PS_Implicit() ) );

        SetDepthStencilState( EnableDepth, 0 );
        SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
    }
}